//=============================================================================
//
//   Copyright (C) 2020 All rights reserved.
//
//   Filename:		point.rs
//
//   Author: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Date: 2020-10-06 14:32
//
//   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Last modified:	2020-11-06 21:20
//
//   Description:
//
//=============================================================================
use crate::dimension::DimType;
use crate::util::Indexable;
use crate::Real;
use nalgebra::{allocator::Allocator, dimension::DimName, DefaultAllocator};

pub trait PointLike<Dim: DimName> {
    fn get_coord(&self) -> nalgebra::VectorN<Real, Dim>
    where
        DefaultAllocator: Allocator<Real, Dim>;
    fn as_np(&self) -> &nalgebra::Point3<Real>;
    fn as_np_mut(&mut self) -> &mut nalgebra::Point3<Real>;
}

///
/// struct point
///
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
pub struct Point<Dim: DimName> {
    id: Option<usize>,
    coord: nalgebra::Point3<Real>,
    #[serde(skip)]
    _marker: std::marker::PhantomData<Dim>,
}

///
/// default
///
impl<Dim: DimName> Default for Point<Dim> {
    #[inline]
    fn default() -> Self {
        Self {
            id: None,
            coord: nalgebra::Point3::<Real>::origin(),
            _marker: std::marker::PhantomData::<Dim> {},
        }
    }
}
impl<Dim: DimName> PointLike<Dim> for Point<Dim> {
    #[inline]
    fn get_coord(&self) -> nalgebra::VectorN<Real, Dim>
    where
        DefaultAllocator: Allocator<Real, Dim>,
    {
        self.coord.coords.fixed_rows::<Dim>(0).clone_owned()
    }
    #[inline]
    fn as_np(&self) -> &nalgebra::Point3<Real> {
        &self.coord
    }
    #[inline]
    fn as_np_mut(&mut self) -> &mut nalgebra::Point3<Real> {
        &mut self.coord
    }
}

impl Point<nalgebra::U2> {
    #[inline]
    pub fn new(x: Real, y: Real) -> Self {
        Self {
            coord: nalgebra::Point3::new(x, y, 0 as Real),
            ..Default::default()
        }
    }
}
impl Point<nalgebra::U3> {
    #[inline]
    pub fn new(x: Real, y: Real, z: Real) -> Self {
        Self {
            coord: nalgebra::Point3::new(x, y, z),
            ..Default::default()
        }
    }
}

indexable_impl!(Point<Dim: DimName>);

pub trait PointPoolLike<DT: DimType>: std::fmt::Debug {
    fn append_point(&mut self, rhs: Point<DT::Dim>) -> &Point<DT::Dim>;
    fn get_point(&self, index: usize) -> &Point<DT::Dim>;
    fn get_point_num(&self) -> usize;
}
pub trait Linked2PointPool<DT: DimType> {
    fn get_point_pool(&self) -> &dyn PointPoolLike<DT>;
    fn set_point_pool(&mut self, rhs: std::rc::Rc<dyn PointPoolLike<DT>>);
}

///
/// test
///
#[cfg(test)]
pub mod tests {
    use super::*;

    use crate::dimension::PlaneStrain;
    use approx::assert_relative_eq;
    use std::collections::HashMap;

    #[derive(Debug)]
    pub struct SomePointPool<DT: DimType> {
        pt_list: HashMap<usize, Point<DT::Dim>>,
    }
    impl<DT: DimType> Default for SomePointPool<DT> {
        fn default() -> Self {
            Self {
                pt_list: HashMap::new(),
            }
        }
    }
    impl<DT: DimType> PointPoolLike<DT> for SomePointPool<DT> {
        #[inline]
        fn append_point(&mut self, rhs: Point<DT::Dim>) -> &Point<DT::Dim> {
            let index = match rhs.get_index() {
                Some(i) => i,
                None => self.pt_list.len(),
            };
            self.pt_list.entry(index).or_insert(rhs);
            self.get_point(index)
        }
        #[inline]
        fn get_point(&self, index: usize) -> &Point<DT::Dim> {
            match self.pt_list.get(&index) {
                Some(p) => p,
                None => panic!("There is no Point #{} in this point pool", index),
            }
        }
        // #[inline]
        // fn get_point_mut(&mut self, index: i32) -> &mut Point<DT::Dim> {
        //     match self.pt_list.get_mut(&index) {
        //         Some(p) => p,
        //         None => panic!("There is no Point #{} in this point pool", index),
        //     }
        // }
        #[inline]
        fn get_point_num(&self) -> usize {
            self.pt_list.len()
        }
    }

    #[test]
    fn serde() {
        let data = r#"{"id":32,"coord":[3.1,9,0]}"#.to_string();
        let v: Point<nalgebra::U2> = serde_json::from_str(&data).unwrap();
        assert_eq!(v.get_index().unwrap(), 32);
        assert_relative_eq!(v.as_np().x, 3.1f32);
        assert_relative_eq!(v.as_np().y, 9f32);
        assert_relative_eq!(v.as_np().z, 0f32);
        assert_eq!(v.get_coord(), nalgebra::Vector2::new(3.1f32, 9f32));
    }
    #[test]
    fn constructor() {
        let vec = vec![7f32, 8f32, 0f32];
        let mut p2 = Point::<nalgebra::U2>::new(7f32, 8f32);
        assert_eq!(p2.as_np().x, vec[0]);
        assert_eq!(p2.as_np().y, vec[1]);
        p2.set_index(123);
        assert_eq!(p2.get_index().unwrap(), 123);
        let p5 = Point::<nalgebra::U3>::new(32f32, 64f32, 0f32);
        assert_eq!(p5.as_np().x, 32f32);
        assert_eq!(p5.as_np().y, 64f32);
    }
    #[test]
    fn point_pool_like() {
        let mut spp = SomePointPool::<PlaneStrain> {
            pt_list: HashMap::new(),
        };
        spp.append_point(Point::<nalgebra::U2>::default());
        let pt1 = Point::<nalgebra::U2>::new(1f32, 8.3f32);
        spp.append_point(pt1);
        assert_eq!(spp.get_point_num(), 2);
        assert_eq!(spp.get_point(0), &Point::<nalgebra::U2>::default());
        assert_eq!(spp.get_point(1), &Point::<nalgebra::U2>::new(1f32, 8.3f32));
    }
}
